home *** CD-ROM | disk | FTP | other *** search
- //
- // CaRMSAPA.c
- // (Change and Restore Monitor Settings - Application Pseudo Alias)
- //
- // A tiny application that acts like an alias to another application,
- // except that it changes the screen depth before launching it, and
- // restores the depth when that application quits.
- //
- // This is designed to be as small and light as possible.
- // Errors reported with the Notification Manager.
- // Target application is set with drag and drop.
- // 'hfnd' resource says what the current target is.
- //
- // It was supposed to be a background-only application,
- // but it didn't work out that way. Calls to
- // SetDepth() don't like being Background-Only.
- // There is still lots of BOA code though.
- //
- // by James Jennings
- // Started October 29, 1995
- //
-
- //#include "APA Testing.h" // routines for testing only
-
- #include "CaRMSAPA.h"
- #include <Palettes.h>
-
- Boolean gDone = false;
- Boolean gRunning = false;
- long gSleepVal = 6000; // Large, since we don't interact with the user.
- short gApplResFileRef = 0;
-
- const short aliasID = 128;
- const OSType ScreenStateType= 'ScnD';
- const short ScreenStateID = 128;
- const short errorMsgListID = 128;
- const short finderHelpStrID= 128;
- const short finderHelpStrTemplateID = 129;
- const short strUnknownID = 130;
-
- ScreenStateRecord savedScreenState;
- ProcessSerialNumber savedProcessSerialNumber;
-
- // Background only applications have a small stack (2K) so we
- // make some Str255's global.
- Str255 errStr; // used in Error();
- Str255 fdrStr; // used in RememberEverything()
-
- // The notification manager record used in Message()
- NMRec notifyRecord;
-
- // controls kind of code in RecallTargetApp()
- #define BACKGROUND_ONLY
-
- void main(void)
- {
- long response;
- OSErr iErr;
- Boolean hasAppleEvents;
- AEEventHandlerUPP RunAEHandlerUPP;
- AEEventHandlerUPP QuitAEHandlerUPP;
- AEEventHandlerUPP OpenAEHandlerUPP;
- AEEventHandlerUPP ChildDiedAEHandlerUPP;
- // Toolbox initialization.
- // QUESTION: Do I need to InitGraf() to support my GDevice calls?
- InitGraf((Ptr) &qd.thePort);
-
- if ( IsBackgroundOnly() ) {
- // I've been crashing. In case it's because the stacks too small, increase it.
- IncreaseApplicationStack(22*1024);
- } else {
- InitFonts();
- // NIM: Processes pg 1-7 says that background-only can't call InitWindows()
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(0L);
- InitCursor();
- }
-
- MaxApplZone();
- // Remember the application's resource fork
- gApplResFileRef = CurResFile();
-
- // Do we have the necessary system features?
- if ( ! ICanRun() ) {
- // If not, abort. We can't do anything.
- Error(errMissingSystemFeature);
- return;
- }
-
- // Install AppleEvent Handlers.
- RunAEHandlerUPP = NewAEEventHandlerProc (&RunAEHandler);
- QuitAEHandlerUPP = NewAEEventHandlerProc (&QuitAEHandler);
- OpenAEHandlerUPP = NewAEEventHandlerProc (&OpenAEHandler);
- ChildDiedAEHandlerUPP = NewAEEventHandlerProc (&ChildDiedAEHandler);
- if (!RunAEHandlerUPP || !OpenAEHandlerUPP || !ChildDiedAEHandlerUPP ) {
- // Couldn't allocate UPP's. Abort.
- Error(errUnexpected);
- return;
- }
- iErr = AEInstallEventHandler(
- kCoreEventClass, // will be called for any event class
- kAEOpenApplication, // will be called for any event ID
- RunAEHandlerUPP, // the handler
- 0, // handler ref con
- false); // not a system handler
- if (iErr!=noErr) { Error(errUnexpected); return; }
- iErr = AEInstallEventHandler(
- kCoreEventClass, // will be called for any event class
- kAEOpenDocuments, // will be called for any event ID
- OpenAEHandlerUPP, // the handler
- 0, // handler ref con
- false); // not a system handler
- if (iErr!=noErr) { Error(errUnexpected); return; }
- iErr = AEInstallEventHandler(
- kCoreEventClass, // will be called for any event class
- kAEApplicationDied, // will be called for any event ID
- ChildDiedAEHandlerUPP, // the handler
- 0, // handler ref con
- false); // not a system handler
- if (iErr!=noErr) { Error(errUnexpected); return; }
-
- #if 0 // insert a bunch of testing routines here
- TestStrings();
- TestScreenState();
- return;
- #endif
-
- // our event loop
- while (!gDone) {
- EventRecord theEvent;
- Boolean gotEvent = WaitNextEvent(everyEvent,
- &theEvent,
- gSleepVal,
- (RgnHandle)nil);
- if (gotEvent && theEvent.what == kHighLevelEvent) {
- // This is a background only app. We only do AppleEvents
- iErr = AEProcessAppleEvent (&theEvent);
- }
- }
- }
-
- /******************** from develop issue 9 ************************************/
-
- /*
- ** Increase the space allocated for the application stack.
- **
- ** Warning: SetApplLimit always sets the stack to at least as large as the
- ** default stack for the machine (8K on machines with original QuickDraw,
- ** 24K on machines with Color QuickDraw), so the application partition
- ** must be large enough to accommodate an appropriate stack and heap.
- **
- ** Call this only once, at the beginning of the application.
- **
- ** Another warning:
- ** Don't bother trying to set the stack size to something lower than 24K.
- ** If SetApplLimit is called to do this, it will silently lower ApplLimit
- ** to a 24K stack.
- */
- OSErr IncreaseApplicationStack(Size incrementSize)
- {
- OSErr result;
-
- /* Increase the stack size by lowering the heap limit. */
- SetApplLimit((Ptr) ((unsigned long) GetApplLimit() - incrementSize));
- result = MemError();
- if ( result == noErr )
- MaxApplZone();
-
- return ( result );
- }
-
- /*****************************************************************************/
-
- inline Boolean EqualPSN(const ProcessSerialNumber *sn1, const ProcessSerialNumber *sn2)
- {
- return sn1->highLongOfPSN==sn2->highLongOfPSN && sn1->lowLongOfPSN==sn2->lowLongOfPSN;
- }
-
- ////////////////////////////////////////
- //
- // AppleEvent Handlers
- //
- ////////////////////////////////////////
- pascal OSErr RunAEHandler (AppleEvent * theEvent, AppleEvent * theReply, long refcon)
- { // Set up the screen depth and launch the child application.
- ScreenStateRecord sd;
- FSSpec file;
- LaunchParamBlockRec launchRec;
-
- // there shouldn't be any required parameters
- OSErr iErr = GotRequiredParam(theEvent);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- if (gRunning) {
- // we only respect the first "run" event
- Error(errLaunchedMoreThanOnce);
- return errAEEventNotHandled;
- }
-
- // get the desired screen state
- sd = RecallScreenState();
- if ( ! ScreenStateAvail(sd) ) {
- Error(errCantSetScreenState);
- return errAEEventNotHandled;
- }
-
- // get the target app's FSSpec
- iErr = RecallTargetApp(&file);
- if (noErr!=iErr) {
- Error(errCantFindApplication);
- return iErr;
- }
-
- // If target is already running, we can't launch it.
- if ( IsAppRunning(&file) ) {
- Error(errTargetAlreadyRunning);
- return errAEEventNotHandled;
- }
-
- // Passed all critical tests. Change the screen if necessary.
- savedScreenState = GetScreenState();
- if ( !EqualScreenState( sd, savedScreenState) ) {
- SetScreenState(sd);
- } else {
- // the screen doesn't need changing: we don't need to hang around
- gDone = true;
- }
-
- // launch application...
- launchRec.launchBlockID = extendedBlock;
- launchRec.launchEPBLength = extendedBlockLen;
- launchRec.launchFileFlags = 0;
- launchRec.launchControlFlags = launchContinue | launchNoFileFlags;
- launchRec.launchAppSpec = &file;
- launchRec.launchAppParameters = 0;
- iErr = LaunchApplication(&launchRec);
- if (iErr != noErr) {
- Error(errUnexpected);
- return iErr;
- }
- // Save the returned process serial number.
- savedProcessSerialNumber = launchRec.launchProcessSN;
-
- gRunning = true;
- return noErr;
- }
-
- pascal OSErr QuitAEHandler (AppleEvent * theEvent, AppleEvent * theReply, long refcon)
- { // Quit. Don't do anything special since this is not normally used.
- OSErr iErr = GotRequiredParam(theEvent);
- if (iErr==noErr)
- Error(errUnexpected);
- gDone = true; // quit no matter what
- return iErr;
- }
-
- pascal OSErr OpenAEHandler (AppleEvent * theEvent, AppleEvent * theReply, long refcon)
- { // Remember an APPL that has been dropped on us.
- AEDesc docList = {typeNull, 0};
- long numberOfItems;
- FSSpec theFSS;
- AEKeyword keywd;
- DescType returnedType;
- Size actualSize;
- OSErr iErr;
-
- gDone = true; // quit when done, no matter what
-
- // Extract the doc list.
- iErr = AEGetParamDesc(theEvent, keyDirectObject, typeAEList, &docList);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- // Have we gotten all the required parameters?
- iErr = GotRequiredParam(theEvent);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- // How many docs?
- iErr = AECountItems(&docList,&numberOfItems);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- if (numberOfItems!=1) {
- Error(errTooManyItemsToOpen);
- return errAEEventNotHandled;
- }
-
- // Extract a FSSpec
- iErr = AEGetNthPtr( &docList, 1, typeFSS,
- &keywd, &returnedType, &theFSS,
- sizeof(theFSS), &actualSize);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- // Check the file type.
- if (GetFileType(&theFSS) != 'APPL') {
- Error(errDroppedFileOfWrongType);
- return errAEEventNotHandled;
- }
- // Everything is hunky-dory
- iErr = RememberEverything(&theFSS);
- if (iErr!=noErr) { Error(errUnexpected); return iErr; }
-
- // fdrStr is set as a side effect of RememberEverything
- Message(fdrStr);
- return noErr;
- }
-
- pascal OSErr ChildDiedAEHandler (AppleEvent * theEvent, AppleEvent * theReply, long refcon)
- { // Restore the screen depth and quit.
- OSErr iErr;
- DescType typeCode;
- Size actualSize;
- long childDiedErrorNumber;
- ProcessSerialNumber psn;
-
- gDone = true; // quit, no matter what
- // There are two required parameters, keyErrorNumber and keyProcessSerialNumber
- iErr = AEGetParamPtr(theEvent, keyErrorNumber,
- typeLongInteger, &typeCode, &childDiedErrorNumber,
- sizeof(long), &actualSize);
- if (iErr!=noErr) {
- Error(errUnexpected);
- return iErr;
- }
- iErr = AEGetParamPtr(theEvent, keyProcessSerialNumber,
- typeProcessSerialNumber, &typeCode, &psn,
- sizeof(ProcessSerialNumber), &actualSize);
- if (iErr!=noErr) {
- Error(errUnexpected);
- return iErr;
- }
- iErr = GotRequiredParam(theEvent);
- if (iErr!=noErr) return iErr;
-
- // Just as a lark, make sure that the child process is the same
- if ( !EqualPSN(&savedProcessSerialNumber, &psn) ) {
- Error(errUnexpected);
- return iErr;
- }
- // restore the screen state
- SetScreenState(savedScreenState);
-
- return noErr;
- }
-
- ////////////////////////////////////////
- //
- // AppleEvent Utilities
- //
- ////////////////////////////////////////
- OSErr GotRequiredParam(AppleEvent *theEvent)
- {
- DescType returnedType;
- Size actualSize;
- OSErr iErr;
- iErr = AEGetAttributePtr( theEvent,
- keyMissedKeywordAttr,
- typeWildCard, &returnedType, // dummy returned type
- 0, // ignore the missed keyword list
- 0, // buffer size (there is no buffer)
- &actualSize); // dummy returned size
- // If no missed keywords are found, then noErr: all required param's have been got.
- if (iErr == errAEDescNotFound)
- iErr = noErr;
- else
- iErr = errAEParamMissed;
-
- return iErr;
- }
-
- ////////////////////////////////////////
- //
- // Screen Property Utilities
- //
- ////////////////////////////////////////
- Boolean EqualScreenState(ScreenStateRecord ss1, ScreenStateRecord ss2)
- {
- return ss1.isColor == ss2.isColor && ss1.theDepth==ss2.theDepth;
- }
-
- ScreenStateRecord GetScreenState(void)
- {
- ScreenStateRecord theState;
- // get the color bit
- GDHandle gdh = GetMainDevice();
- theState.isColor = TestDeviceAttribute(gdh, gdDevType);
- // get the depth (look at the GDevice's pixel map record)
- theState.theDepth = (**((**gdh).gdPMap)).pixelSize;
-
- return theState;
- }
-
- Boolean ScreenStateAvail(ScreenStateRecord theState)
- {
- return HasDepth( GetMainDevice(), theState.theDepth, gdDevType, theState.isColor);
- }
-
- void SetScreenState(ScreenStateRecord theState)
- {
- OSErr iErr;
- iErr = SetDepth( GetMainDevice(), // the GDevice to change
- theState.theDepth, // the new depth
- 1<<gdDevType, // the whichFlags value for setting b&w/color
- theState.isColor); // 0 = b&w, 1 = color
- if (iErr!=noErr) {
- Error(errUnexpected);
- return;
- }
- }
-
- void RememberScreenState(ScreenStateRecord theState)
- {
- OSErr iErr;
- ScreenStateHdl sdh = (ScreenStateHdl)Get1Resource(ScreenStateType, ScreenStateID);
- if (sdh) {
- **sdh = theState;
- ChangedResource((Handle)sdh);
- } else // The resource should have been there.
- Error(errUnexpected);
- }
-
- ScreenStateRecord RecallScreenState(void)
- {
- ScreenStateHdl sdh = (ScreenStateHdl)Get1Resource(ScreenStateType, ScreenStateID);
- if (sdh) return **sdh;
- // we shouldn't get here, but if we do, return something reasonable
- Error(errUnexpected);
- return GetScreenState();
- }
-
-
- ////////////////////////////////////////
- //
- // String Utilities
- // Pretty kludgy, but it should handle two-byte script systems.
- //
- ////////////////////////////////////////
- inline void StringCopy(Str255 src, Str255 dst)
- {
- BlockMove(src,dst,src[0]+1);
- }
- inline void StringAppend(Str255 add, Str255 dest)
- {
- BlockMove(add+1,dest+dest[0]+1,add[0]);
- dest[0] += add[0];
- }
- void StringTemplate(Str255 tmp, Str255 sub1, Str255 sub2)
- { // substitute
- Handle t,s;
- Size len;
- if (tmp==0) return; // sanity check
-
- // convert string to a handle
- if ( noErr!=PtrToHand(tmp+1, &t,tmp[0]) ) return;
-
- if (sub1) {
- if ( noErr!=PtrToHand(sub1+1, &s, sub1[0]) ) goto abort;
- ReplaceText(t, s, "\p^0"); // note: we ignore the number of substitutions
- DisposeHandle(s);
- }
- if (sub2) {
- if ( noErr!=PtrToHand(sub2+1, &s,sub2[0]) ) goto abort;
- ReplaceText(t, s, "\p^1"); // note: we ignore the number of substitutions
- DisposeHandle(s);
- }
- abort:
- len = GetHandleSize(t);
- if (len>255) len=255; // clip the string
- BlockMove(*t,tmp+1,len);
- tmp[0] = len;
- DisposeHandle(t);
- }
-
- void ScreenStateToString(ScreenStateRecord sd, Str63 str)
- {
- StringHandle stringHdl, colorHdl;
- Str63 num;
- const short baseID = 1000;
- const short grayID = 998, colorID = 999;
- short depthID = baseID + sd.theDepth;
- str[0] = 0; // zero the string
-
- colorHdl = GetString( sd.isColor? colorID : grayID );
- stringHdl = GetString(depthID); // depth as string: 256, thousands, millions, etc
-
- if (stringHdl) {
- StringCopy(*stringHdl,str);
- if (sd.theDepth==1) return;
- HLock((Handle)colorHdl);
- StringTemplate(str,0,*colorHdl);
- HUnlock((Handle)colorHdl);
- } else {
- // didn't find it. Build it.
- stringHdl = GetString(baseID);
- if (stringHdl==0) return; // still can't find it: abort
- StringCopy(*stringHdl,str);
- NumToString(sd.theDepth,num);
- HLock((Handle)colorHdl);
- StringTemplate(str,num,*colorHdl);
- HUnlock((Handle)colorHdl);
- }
- }
-
- void AliasToFileName(AliasHandle theAlias, Str63 name)
- {
- StringHandle sh;
- if (GetAliasInfo(theAlias, asiAliasName, name)==noErr) return;
- // sensible default behaviour
- sh = GetString(strUnknownID);
- if (sh)
- StringCopy(*sh,name);
- else
- name[0] = 0;
- }
-
- void MakeFinderHelpString(Str255 s)
- {
- AliasHandle ah;
- ScreenStateHdl sdh;
- StringHandle sh;
- Str63 screen, file; // Small for BOA
-
- ah = (AliasHandle)Get1Resource(rAliasType, aliasID);
- sdh = (ScreenStateHdl)Get1Resource(ScreenStateType, ScreenStateID);
- AliasToFileName(ah, file);
- ScreenStateToString(**sdh, screen);
- sh = GetString(finderHelpStrTemplateID);
- if (sh) {
- StringCopy(*sh, s);
- StringTemplate(s, screen, file);
- }
- }
-
- ////////////////////////////////////////
- //
- // Alias Utilities
- //
- ////////////////////////////////////////
- OSType GetFileType(FSSpec *spec)
- {
- FInfo fndrInfo;
- OSErr err = HGetFInfo (spec->vRefNum, spec->parID, spec->name, &fndrInfo);
- if (err) return '???\?';
- return fndrInfo.fdType;
- }
-
- Boolean EqualFSSpec(FSSpec *f1, FSSpec *f2); // inline eventually
- Boolean EqualFSSpec(FSSpec *f1, FSSpec *f2)
- { // are two FSSpecs equal?
- if (f1->vRefNum != f2->vRefNum) return false;
- if (f1->parID != f2->parID) return false;
- return EqualString(f1->name,f2->name,false,false);
- }
-
- OSErr RememberTargetApp(FSSpec* f)
- { // save an alias to the file in the resource fork
- AliasHandle ah, ahr;
- Size len;
- OSErr iErr;
-
- iErr = NewAlias(0,f,&ah);
- if (noErr!=iErr) return iErr;
- if (ah) {
- // remove the old resource, if any
- ahr = (AliasHandle) Get1Resource(rAliasType,aliasID);
- if (ahr) RemoveResource((Handle)ahr);
- // add the alias to the resource
- AddResource((Handle)ah, rAliasType, aliasID, 0);
- iErr = ResError();
- }
- return iErr;
- }
-
- OSErr RecallTargetApp(FSSpec *f)
- { // recover the file spec from the alias resource
- AliasHandle ah;
- Boolean wasChanged = false;
- OSErr iErr = noErr;
- #ifdef BACKGROUND_ONLY
- Boolean needsUpdate = false;
- short aliasCount = 1; // only search for one match
- #endif
-
- ah = (AliasHandle) Get1Resource(rAliasType,aliasID);
- #ifdef BACKGROUND_ONLY
- // Note: ResolveAlias() can't be used in a BOA. Use MatchAlias() instead.
- iErr = MatchAlias(0,// fromFile for relative path
- // rulesMask == no user interaction + fast search
- kARMNoUI + kARMSearch,
- ah, // the alias to resolve
- &aliasCount,// allowed number of results == 1
- f, // aliasList,
- &needsUpdate,// needsUpdate
- 0, // no filter function,
- 0 // no yourDataPtr for the filter function
- );
- if (iErr!=noErr) return iErr;
-
- if (needsUpdate) { // save any changes
- iErr = UpdateAlias(0, f, ah, &wasChanged);
- if (iErr==noErr && wasChanged) // save any changes
- ChangedResource((Handle)ah);
- }
- #else
- iErr = ResolveAlias(0, ah, f, &wasChanged);
- if (noErr == iErr) {
- if (wasChanged) // save any changes
- ChangedResource((Handle)ah);
- }
- #endif
- return iErr;
- }
-
- OSErr RememberEverything(FSSpec *spec)
- {
- OSErr iErr;
- StringHandle sh;
-
- // UseResFile(gApplResFileRef); // shouldn't be necessary.
- iErr = RememberTargetApp(spec);
- if ( noErr != iErr ) return iErr;
- RememberScreenState(GetScreenState());
- MakeFinderHelpString(fdrStr);
- sh = GetString(finderHelpStrID);
- if (sh) {
- SetHandleSize((Handle)sh, fdrStr[0]+1 );
- if (MemError()!=noErr) return MemError();
- StringCopy(fdrStr,*sh);
- ChangedResource((Handle)sh);
- }
- UpdateResFile(gApplResFileRef);
- return noErr;
- }
-
- Boolean IsAppRunning(FSSpec *theApp)
- { // is the app already launched?
- FSSpec procFSS;
- OSErr iErr;
- ProcessSerialNumber psn;
- ProcessInfoRec pir;
-
- // loop through the processes, looking for ours
- psn.highLongOfPSN = 0;
- psn.lowLongOfPSN = kNoProcess;
- pir.processInfoLength = sizeof(ProcessInfoRec);
- pir.processName = 0; // don't want the name
- pir.processAppSpec = &procFSS; // file spec of the process
-
- for (iErr = GetNextProcess(&psn); iErr!=procNotFound; iErr = GetNextProcess(&psn)) {
- // loop through the processes, looking for something that matches our file
- iErr = GetProcessInformation(&psn, &pir);
- if (iErr==noErr)
- if ( EqualFSSpec(theApp, &procFSS) )
- return true;
- }
- // didn't find it (thank goodness)
- return false;
- }
-
- ////////////////////////////////////////
- //
- // Error Messages
- //
- ////////////////////////////////////////
- void Error(ErrorCode code)
- { // Report an error
- GetIndString(errStr, errorMsgListID, code);
- Message(errStr);
- gDone = true;
- }
-
- static void Resp(NMRec *rec)
- {
- OSErr iErr = NMRemove(rec); // remove the notification
- rec->nmRefCon = 0; // stop waiting for a response
- }
-
- void Message(Str255 str)
- { // Send message to the user using the notification manager.
- OSErr iErr;
- // DebugStr(str);
- notifyRecord.qType = nmType;
- notifyRecord.nmMark = 0; // no mark
- notifyRecord.nmIcon = 0; // no icon
- notifyRecord.nmSound = 0; // no sound
- notifyRecord.nmStr = str; // Don't release this memory!
- // notifyRecord.nmResp = (void*)-1; // -1 = automaticly remove from queue when done
- // why do we need this cast?
- notifyRecord.nmResp = (void*)Resp; // response proc
- notifyRecord.nmRefCon = 1; // Resp Proc sets this to 0 when done.
-
- iErr = NMInstall(¬ifyRecord);
- if (iErr!=noErr) return; // sanity check
- // Hang around until the notification is aknowledged.
- while (notifyRecord.nmRefCon) {
- EventRecord theEvent;
- WaitNextEvent(0/*no events*/, &theEvent, 6/*sleep*/, 0);
- }
- }
-
- Boolean ICanRun(void)
- { // Test to see if the system supports the things we need.
- Boolean hasAppleEvents, hasNotification;
- long response;
- OSErr iErr;
-
- // test for AppleEvents
- iErr = Gestalt(gestaltAppleEventsAttr, &response);
- hasAppleEvents = (iErr == noErr) && (response & 1 << gestaltAppleEventsPresent);
- if (!hasAppleEvents) return false;
-
- // test for notification manager
- iErr = Gestalt(gestaltNotificationMgrAttr, &response);
- hasNotification = (iErr == noErr) && (response & 1 << gestaltNotificationPresent);
- if (!hasNotification) return false;
-
- return true;
- }
-
- Boolean IsBackgroundOnly(void)
- { // Examine the SIZE -1 to see if we're background only.
- typedef struct { short flags; long prefered; long minimum; } SIZERec, **SIZEHdl;
- const long BOMask = 0x0400;
- SIZEHdl h = (SIZEHdl) GetResource('SIZE', -1);
- if (h==0) return false;
-
- return ((**h).flags & BOMask) != 0;
- }
-